package com.guinea.core.web;

import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.sf.navigator.menu.MenuComponent;
import net.sf.navigator.menu.MenuRepository;

import org.apache.commons.beanutils.ConvertUtils;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionMessages;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.ReflectionUtils;

import com.guinea.core.orm.HibernateDao;
import com.guinea.core.orm.Page;
import com.guinea.core.orm.PropertyFilter;
import com.guinea.core.service.EntityService;
import com.guinea.core.utils.BeanUtils;
import com.guinea.core.utils.ServletUtils;

/**
 * 负责管理单个Entity CRUD操作的Struts Action基类. <p/> 子类以以下方式声明,并实现将拥有默认的CRUD函数 <p/>
 * 
 * <pre>
 *              public class UserAction extends StrutsEntityAction&lt;User, UserDao&gt; {
 *              	private UserDao userDao;
 *            &lt;p/&gt;
 *              	public void setUserDao(UserDao userDao) {
 *              		this.userDao = userDao;
 *              	}
 *              }
 * </pre>
 * 
 * <p/> 此类仅演示一种封装的方式，大家可按自己的项目习惯进行重新封装 <p/> 目前封装了：<br/>
 * 1.index、list、create、edit、view、save、delete 七种action的流程封装；<br/>
 * 2.doListEntity、doGetEntity、doNewEntity、doSaveEntity(),doDeleteEntity
 * 五种业务函数调用，可在子类重载；<br/>
 * 3.initEntity、initForm两个FormBean与业务对象的初始函数及refrenceData,onInitForm,
 * onInitEntity 三个回调函数；<br/> 4.savedMessage、deletedMessage 两种业务成功信息，可在子类重载。<br/>
 * 
 * @author calvin
 */
public abstract class StrutsEntityAction extends StrutsAction implements
		InitializingBean {
	protected Logger logger = LoggerFactory.getLogger(getClass());

	protected static final String LIST = "list";

	protected static final String EDIT = "edit";

	protected static final String VIEW = "view";

	protected static final String REQUEST_PAGE = "page";

	protected static final String REQUEST_ENTITY = "entity";

	protected static final String FILTER_PREFIX = "filter";

	protected static final String FILTER_MAP = "filters";

	protected Class idClass; // Action所管理的Entity的主键类型.

	protected String idName; // Action所管理的Entity的主键名.

	protected String[] methodNames;

	protected Object entity;

	public abstract EntityService getEntityService();

	/**
	 * url参数未定义method时的默认Action函数. 默认为index Action.
	 */
	public ActionForward unspecified(ActionMapping mapping, ActionForm form,
			HttpServletRequest request, HttpServletResponse response) {
		MenuRepository r = (MenuRepository)request.getSession().getServletContext().getAttribute("repository");
		MenuComponent c = r.getMenu("root");
		return list(mapping, form, request, response);
	}

	/**
	 * 列出所有对象的Action函数.
	 */
	public ActionForward list(ActionMapping mapping, ActionForm form,
			HttpServletRequest request, HttpServletResponse response) {
		// 从request中获取含属性前缀名的参数,构造去除前缀名后的参数Map.
		Map filterParamMap = ServletUtils.getParametersStartingWith(request,
				FILTER_PREFIX + "_");
		List filters = PropertyFilter.buildFromParaMap(filterParamMap);
		Page page = PropertyFilter.buildPageFromHttpRequest(request,
				REQUEST_PAGE);

		page = getEntityService().findPage(page, filters);

		request.setAttribute(REQUEST_PAGE, page);
		request.setAttribute(FILTER_MAP, filterParamMap);
		return mapping.findForward(LIST);
	}

	/**
	 * 显示修改对象Form的Action函数.
	 */
	public ActionForward edit(ActionMapping mapping, ActionForm form,
			HttpServletRequest request, HttpServletResponse response) {
		// 防止重复提交的token
		saveToken(request);

		bindForm(form, entity);

		return mapping.findForward(EDIT);
	}

	/**
	 * 保存对象的Action函数.
	 */
	public ActionForward save(ActionMapping mapping, ActionForm form,
			HttpServletRequest request, HttpServletResponse response) {

		doInitEntity(form);
		
		doSaveEntity(request);

		return mapping.findForward(SUCCESS);
	}

	/**
	 * 保存前的验证.
	 */
	public ActionForward saveValidator(ActionMapping mapping, ActionForm form,
			HttpServletRequest request, HttpServletResponse response) {
		// 验证TOKEN，防止重复提交
		if (!validateToken(request))
			return mapping.findForward(LIST);

		ActionMessages errors = form.validate(mapping, request);
		if (!errors.isEmpty()) {
			saveErrors(request, errors);
			resetToken(request);
			// 防止重复提交的token
			saveToken(request);
			return mapping.findForward(EDIT);
		}

		return null;
	}

	/**
	 * 查看业务对象（不能修改）的Action函数.
	 */
	public ActionForward view(ActionMapping mapping, ActionForm form,
			HttpServletRequest request, HttpServletResponse response) {

		request.setAttribute(REQUEST_ENTITY, entity);

		return mapping.findForward(VIEW);
	}

	/**
	 * 标记删除单个对象的Action函数.
	 */
	public ActionForward erase(ActionMapping mapping, ActionForm form,
			HttpServletRequest request, HttpServletResponse response) {

		doEraseEntity(request, entity);

		return mapping.findForward(SUCCESS);
	}

	/**
	 * 验证TOKEN
	 * 
	 * @param request
	 * @return
	 */
	protected boolean validateToken(HttpServletRequest request) {
		if (isCancelled(request))
			return false;
		if (!isTokenValid(request)) {
			saveError(request, "submit.duplicated");
			return false;
		}
		resetToken(request);
		return true;
	}

	/**
	 * 初始化Entity，用于edit,save,view,erase等操作
	 */
	protected ActionForward cannotMissEntity(ActionMapping mapping,
			HttpServletRequest request) {
		entity = null;
		Serializable id = getEntityId(request);
		if (id != null) {
			entity = doGetEntity(request, id);
			if (entity == null) {
				saveError(request, "entity.missing");
				return mapping.findForward(LIST);
			}
		}

		return null;
	}

	protected Serializable getEntityId(HttpServletRequest request) {
		Serializable id = null;
		String idString = request.getParameter(idName);
		try {
			id = (Serializable) ConvertUtils.convert(idString, idClass);
		} catch (NumberFormatException e) {
			throw new IllegalArgumentException("Wrong when get id from request");
		}
		return id;
	}

	/**
	 * 新建业务对象的函数.
	 */
	protected Object doNewEntity() {
		Object object = null;
		try {
			object = getEntityClass().newInstance();
		} catch (Exception e) {
			log.error("Can't new Instance of entity.", e);
		}
		return object;
	}

	/**
	 * 从数据库获取业务对象的函数.
	 */
	protected Object doGetEntity(HttpServletRequest request, Serializable id) {
		return getEntityService().get(id);
	}

	/**
	 * 初始化业务对象的函数.
	 */
	protected void doInitEntity(ActionForm form) {
		if (entity == null) {
			entity = doNewEntity();
		}
		bindEntity(form, entity);
	}
	
	/**
	 * 保存业务对象的函数.
	 */
	protected void doSaveEntity(HttpServletRequest request) {
		getEntityService().save(entity);
		saveMessage(request, "entity.saved");
	}

	/**
	 * 标记删除业务对象的函数.
	 */
	protected void doEraseEntity(HttpServletRequest request, Object object) {
		getEntityService().erase(object);
		saveMessage(request, "entity.deleted");
	}

	/**
	 * 取得entityClass的函数.
	 */
	protected Class getEntityClass() {
		return getEntityService().getEntityDao().getEntityClass();
	};

	/**
	 * Init回调函数,初始化一系列参数.
	 */
	public void afterPropertiesSet() {
		EntityService entityService = getEntityService();
		HibernateDao entityDao = entityService.getEntityDao();
		// 反射获得entity的主键类型
		try {
			idName = entityDao.getIdName();
			idClass = BeanUtils.getPropertyType(getEntityClass(), idName);
		} catch (Exception e) {
			ReflectionUtils.handleReflectionException(e);
		}
		Method[] ms = getClass().getMethods();
		methodNames = new String[ms.length];
		for (int i = 0; i < ms.length; i++) {
			methodNames[i] = ms[i].getName();
		}
	}

	private Method findMethod(String name) throws NoSuchMethodException {
		Method m = null;
		for (int i = 0; i < methodNames.length; i++) {
			if (methodNames[i].equals(name)) {
				m = getMethod(name);
				break;
			}
		}
		return m;
	}

	/**
	 * Process the specified HTTP request, and create the corresponding HTTP
	 * response (or forward to another web component that will create it).
	 * Return an <code>ActionForward</code> instance describing where and how
	 * control should be forwarded, or <code>null</code> if the response has
	 * already been completed.
	 * 
	 * @param mapping
	 *            The ActionMapping used to select this instance
	 * @param form
	 *            The optional ActionForm bean for this request (if any)
	 * @param request
	 *            The HTTP request we are processing
	 * @param response
	 *            The HTTP response we are creating
	 * 
	 * @exception Exception
	 *                if an exception occurs
	 */
	public ActionForward execute(ActionMapping mapping, ActionForm form,
			HttpServletRequest request, HttpServletResponse response)
			throws Exception {
		if (isCancelled(request)) {
			ActionForward af = cancelled(mapping, form, request, response);
			if (af != null) {
				return af;
			}
		}

		// Get the parameter. This could be overridden in subclasses.
		String parameter = getParameter(mapping, form, request, response);

		// Get the method's name. This could be overridden in subclasses.
		String name = getMethodName(mapping, form, request, response, parameter);

		// Prevent recursive calls
		if ("execute".equals(name) || "perform".equals(name)) {
			String message = messages.getMessage("dispatch.recursive", mapping
					.getPath());

			log.error(message);
			throw new ServletException(message);
		}

		// Invoke the named method, and return the result
		ActionForward result = cannotMissEntity(mapping, request);
		if (result == null)
			result = validateMyMethod(mapping, form, request, response, name
					+ "Validator");
		if (result == null)
			result = dispatchMethod(mapping, form, request, response, name);

		if (result != null) {
			name = result.getName();
			renderMyMethod(mapping, form, request, response, name + "Render");
		}
		return result;
	}

	/**
	 * Dispatch to the specified method.
	 * 
	 * @since Struts 1.1
	 */
	protected ActionForward validateMyMethod(ActionMapping mapping,
			ActionForm form, HttpServletRequest request,
			HttpServletResponse response, String name) throws Exception {

		// Make sure we have a valid method name to call.
		// This may be null if the user hacks the query string.
		if (name == null) {
			return null;
		}
		// Identify the method object to be dispatched to
		Method method = null;
		try {
			method = findMethod(name);
		} catch (NoSuchMethodException e) {
			String message = messages.getMessage("validateMyMethod.method",
					mapping.getPath(), name);
			log.error(message, e);

			String userMsg = messages.getMessage(
					"validateMyMethod.method.user", mapping.getPath());
			throw new NoSuchMethodException(userMsg);
		}
		if (method == null)
			return null;

		ActionForward forward = null;
		try {
			Object args[] = { mapping, form, request, response };
			forward = (ActionForward) method.invoke(this, args);

		} catch (ClassCastException e) {
			String message = messages.getMessage("validateMyMethod.return",
					mapping.getPath(), name);
			log.error(message, e);
			throw e;

		} catch (IllegalAccessException e) {
			String message = messages.getMessage("validateMyMethod.error",
					mapping.getPath(), name);
			log.error(message, e);
			throw e;

		} catch (InvocationTargetException e) {
			// Rethrow the target exception if possible so that the
			// exception handling machinery can deal with it
			Throwable t = e.getTargetException();
			if (t instanceof Exception) {
				throw ((Exception) t);
			} else {
				String message = messages.getMessage("validateMyMethod.error",
						mapping.getPath(), name);
				log.error(message, e);
				throw new ServletException(t);
			}
		}

		// Return the returned ActionForward instance
		return (forward);
	}

	/**
	 * Dispatch to the specified method.
	 * 
	 * @since Struts 1.1
	 */
	protected void renderMyMethod(ActionMapping mapping, ActionForm form,
			HttpServletRequest request, HttpServletResponse response,
			String name) throws Exception {

		// Make sure we have a valid method name to call.
		// This may be null if the user hacks the query string.
		if (name == null) {
			return;
		}
		// Identify the method object to be dispatched to
		Method method = null;
		try {
			method = findMethod(name);
		} catch (NoSuchMethodException e) {
			String message = messages.getMessage("renderMyMethod.method",
					mapping.getPath(), name);
			log.error(message, e);

			String userMsg = messages.getMessage("renderMyMethod.method.user",
					mapping.getPath());
			throw new NoSuchMethodException(userMsg);
		}
		if (method == null)
			return;

		try {
			Object args[] = { mapping, form, request, response };
			method.invoke(this, args);

		} catch (ClassCastException e) {
			String message = messages.getMessage("renderMyMethod.return",
					mapping.getPath(), name);
			log.error(message, e);
			throw e;

		} catch (IllegalAccessException e) {
			String message = messages.getMessage("renderMyMethod.error",
					mapping.getPath(), name);
			log.error(message, e);
			throw e;

		} catch (InvocationTargetException e) {
			// Rethrow the target exception if possible so that the
			// exception handling machinery can deal with it
			Throwable t = e.getTargetException();
			if (t instanceof Exception) {
				throw ((Exception) t);
			} else {
				String message = messages.getMessage("renderMyMethod.error",
						mapping.getPath(), name);
				log.error(message, e);
				throw new ServletException(t);
			}
		}
	}

}
